package com.carleasoft.mps.data.redis.impl;


import cn.hutool.json.JSONUtil;
import com.carleasoft.mps.data.redis.IRedisDataSource;
import com.carleasoft.mps.data.redis.JedisService;
import com.carleasoft.mps.data.redis.base.ICacheKey;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import redis.clients.jedis.Jedis;

import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
 * redis工具类服务实现
 * @author: haoyi
 */
@Service("redisService")
public class RedisServiceImpl implements JedisService {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private IRedisDataSource redisDataSource;
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        StringRedisSerializer keySerializer = new StringRedisSerializer();
        GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(new ObjectMapper());
        redisTemplate.setKeySerializer(keySerializer);
        redisTemplate.setValueSerializer(valueSerializer);
        redisTemplate.setHashKeySerializer(keySerializer);
        redisTemplate.setHashValueSerializer(valueSerializer);
        return redisTemplate;
    }

    /**
     * 添加缓存 实现步骤:(value作为String保存到缓存)
     * @param cacheKey
     * @param key
     * @param value
     * @return String
     */
    @Override
    public String addString(ICacheKey cacheKey, String key, String value) {
        log.debug(" process in RedisServiceImpl.addString method, parameter keyEnum: " + cacheKey + ", key: " + key + ", value: " + value);

        String result = null;
        Jedis jedis = redisDataSource.getRedisClient();
        if (jedis != null) {
            String redisKey = this.generateKey(cacheKey, key);
            try {
                result = jedis.set(redisKey, value);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            } finally {
                redisDataSource.returnResource(jedis);
            }
        }

        return result;
    }

    @Override
    public String addString(String key, String value) {
        return null;
    }

    /**
     * 添加缓存 实现步骤:(value作为String保存到缓存)
     * @param cacheKey
     * @param key
     * @param value
     * @param seconds 失效时长(秒)
     * @return String
     */
    @Override
    public String addString(ICacheKey cacheKey, String key, String value, Integer seconds) {
        log.debug(" process in RedisServiceImpl.addString method, parameter cacheKey: " + cacheKey + ", key: " + key + ", value: " + value);

        String result = null;
        Jedis jedis = redisDataSource.getRedisClient();
        if (jedis != null) {
            String redisKey = this.generateKey(cacheKey, key);
            try {
                result = jedis.setex(redisKey,seconds,value);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            } finally {
                redisDataSource.returnResource(jedis);
            }
        }

        return result;
    }

    @Override
    public String addString(ICacheKey cacheKey, String key, String value, Integer seconds, Integer db) {
        return null;
    }

    /**
     * 获取缓存 实现步骤:获取value为string类型
     * @param cacheKey
     * @param key
     * @return String
     */
    @Override
    public String getString(ICacheKey cacheKey, String key) {
        log.debug(" process in RedisServiceImpl.getString method, parameter cacheKey: " + cacheKey + ", key: " + key);
        String result = null;
        Jedis jedis = redisDataSource.getRedisClient();
        if (jedis != null) {
            String redisKey = this.generateKey(cacheKey, key);
            try {
                result = jedis.get(redisKey);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            } finally {
                redisDataSource.returnResource(jedis);
            }
        }
        return result;
    }

    @Override
    public String getString(String key) {
        return null;
    }

    /**
     * 删除缓存 实现步骤:获取value为string类型
     * @param cacheKey
     * @param key
     * @return Long 被删除 key 的数量
     */
    @Override
    public Long deleteString(ICacheKey cacheKey, String key) {
        log.debug(" process in RedisServiceImpl.deleteString method, parameter cacheKey: " + cacheKey + ", key: " + key);

        Long result = null;
        Jedis jedis = redisDataSource.getRedisClient();
        if (jedis != null) {
            String redisKey = this.generateKey(cacheKey, key);
            try {
                result =jedis.del(redisKey);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            } finally {
                redisDataSource.returnResource(jedis);
            }
        }
        return result;
    }

    @Override
    public Long deleteString(ICacheKey cacheKey, String key, Integer db) {
        return null;
    }

    /**
     * 方法用途: 添加缓存 实现步骤:(Object会转为byte保存在缓存)
     * @param cacheKey
     * @param key
     * @return String
     */
    @Override
    public String addObject(ICacheKey cacheKey, String key, Object value) {
        log.debug(" process in RedisServiceImpl.addObject method, parameter cacheKey: " + cacheKey + ", key: " + key + ", value: " + value);
        String result = null;

        Jedis jedis = redisDataSource.getRedisClient();
        if (jedis != null) {
            String redisKey = this.generateKey(cacheKey, key);
            try {
                result = jedis.set(redisKey.getBytes(), this.toByteArray(value));
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            } finally {
                redisDataSource.returnResource(jedis);
            }
        }
        return result;
    }
    /**
     * 方法用途: 添加缓存 实现步骤:(Object会转为byte保存在缓存)
     * @param cacheKey
     * @param key
     * @param seconds 失效时长(秒)
     * @return String
     */
    @Override
    public String addObject(ICacheKey cacheKey, String key, Object value, Integer seconds) {
        log.debug(" process in RedisServiceImpl.addObject method, parameter cacheKey: " + cacheKey + ", key: " + key + ", value: " + value);

        String result = null;
        Jedis jedis = redisDataSource.getRedisClient();
        if (jedis != null) {
            String redisKey = this.generateKey(cacheKey, key);
            try {
                result = jedis.setex(redisKey.getBytes(),seconds,toByteArray(value));
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            } finally {
                redisDataSource.returnResource(jedis);
            }
        }

        return result;
    }
    /**
     * 获取缓存 实现步骤:获取value为Object类型
     * @param cacheKey
     * @param key
     * @return Object
     */
    @Override
    public Object getObject(ICacheKey cacheKey, String key) {
        log.debug(" process in RedisServiceImpl.getObject method, parameter cacheKey: " + cacheKey + ", key: " + key);
        Object result = null;
        Jedis jedis = redisDataSource.getRedisClient();
        if (jedis != null) {
            String redisKey = this.generateKey(cacheKey, key);
            try {
                byte[] data = jedis.get(redisKey.getBytes());
                result = this.toObject(data);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            } finally {
                redisDataSource.returnResource(jedis);
            }
        }
        return result;
    }
    /**
     * 删除缓存 实现步骤:获取value为Object类型
     * @param cacheKey
     * @param key
     * @return Long 被删除 key 的数量
     */
    @Override
    public Long deleteObject(ICacheKey cacheKey, String key) {
        log.debug(" process in RedisServiceImpl.deleteObject method, parameter cacheKey: " + cacheKey + ", key: " + key);

        Long result = null;
        Jedis jedis = redisDataSource.getRedisClient();
        if (jedis != null) {
            String redisKey = this.generateKey(cacheKey, key);
            try {
                result =jedis.del(redisKey.getBytes());
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            } finally {
                redisDataSource.returnResource(jedis);
            }
        }
        return result;
    }

    /**
     * 缓存中是否存在这个Key
     * @param cacheKey
     * @param key
     * @return Boolean
     */
    @Override
    public Boolean exists(ICacheKey cacheKey, String key) {
        log.debug(" process in RedisServiceImpl.exists method, parameter cacheKey: " + cacheKey + ", key: " + key);

        Boolean result = false;

        Jedis jedis = redisDataSource.getRedisClient();
        if (jedis != null) {
            String redisKey = this.generateKey(cacheKey, key);
            try {
                result = jedis.exists(redisKey);
                if(!result){
                    result = jedis.exists(redisKey.getBytes());
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);

            } finally {
                redisDataSource.returnResource(jedis);
            }
        }
        return result;
    }

    /**
     * 根据缓存类型获取类型下所以的key值集合
     * @param cacheKey
     * @return List
     */
    @Override
    public List<String> keys(ICacheKey cacheKey) {
        log.debug(" process in RedisServiceImpl.keys method, parameter cacheKey: " + cacheKey);
        List<String> result = new ArrayList<String>();

        String wildcard = cacheKey.getModuleService() + cacheKey.getFunctionInfo()+"*";

        Jedis jedis = redisDataSource.getRedisClient();
        if (jedis != null) {
            try {
                Collection<String> keys = jedis.keys(wildcard);
                result.addAll(keys);
            } catch (Exception e) {
                log.error(e.getMessage(), e);

            } finally {
                redisDataSource.returnResource(jedis);
            }
        }

        return result;
    }

    /**
     * 分布式锁,使用做交易锁竞争较好，无死锁风险,过期时间秒
     * @param cacheKey
     * @param key
     * @return Boolean
     */
    @Override
    public boolean dcsLock(ICacheKey cacheKey, String key, String value, long expireSeconds) {
        log.debug(" process in RedisServiceImpl.dcsLock method, parameter cacheKey: " + cacheKey + ", key: " + key);

        Boolean result = false;
        Jedis jedis = redisDataSource.getRedisClient();
        if (jedis != null) {
            try {
                String redisResult = jedis.set(this.generateKey(cacheKey, key), value, "NX", "EX", expireSeconds);
                if ("OK".equals(redisResult)) {
                    result = true;
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);

            } finally {
                redisDataSource.returnResource(jedis);
            }
        }
        return result;
    }

    /**
     * 释放分布式锁,传入value，体现原汤化原食，解铃还须系铃人思维
     * @param cacheKey
     * @param key
     * @return Boolean
     */
    @Override
    public boolean releaseDcsLock(ICacheKey cacheKey, String key, String value) {
        Boolean result = false;
        Jedis jedis = redisDataSource.getRedisClient();
        if (jedis != null) {
            String redisKey = this.generateKey(cacheKey, key);
            try {
                if (value.equals(jedis.get(redisKey))) {
                    jedis.del(redisKey);
                    result = true;
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);

            } finally {
                redisDataSource.returnResource(jedis);
            }
        }

        return result;
    }

    @Override
    public List<String> hmget(String key, List<String> ids) {
        List<String> result = null;
        Jedis jedis = redisDataSource.getRedisClient();
        if (jedis != null) {
            try {
                result =  jedis.hmget(key, ids.toArray(new String[]{}));
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            } finally {
                redisDataSource.returnResource(jedis);
            }
        }

        return result;
    }

    @Override
    public void hmset(String key, List<Object> objects,List<String> ids) {
        Long result;
        Jedis jedis = redisDataSource.getRedisClient();
        if (jedis != null) {
            try {
                for (int i = 0; i < objects.size(); i++) {
                    result =  jedis.hset(key,ids.get(i), JSONUtil.toJsonStr(objects.get(i)));
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);

            } finally {
                redisDataSource.returnResource(jedis);
            }
        }
    }

    @Override
    public void hmset(String key, Object object,String id) {
        Jedis jedis = redisDataSource.getRedisClient();
        if (jedis != null) {
            try {
                if (Objects.isNull(object)){
                    jedis.hset(key, id, "{}");
                }else {
                    jedis.hset(key, id, JSONUtil.toJsonStr(object));
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);

            } finally {
                redisDataSource.returnResource(jedis);
            }
        }
    }

    @Override
    public RedisTemplate<String, Object> getTemp() {
        return redisTemplate;
    }

    /**
     * 设置redis的key的规则
     */
    private String generateKey(ICacheKey keyEnum, String key) {
        String redisKey = keyEnum.getModuleService() + keyEnum.getFunctionInfo() + (StringUtils.isEmpty(key) ? "" : key);
        log.debug(" redisKey: " + redisKey);
        return redisKey;
    }
    /**
     * 描述 : Object转byte
     * @param obj
     * @return byte[]
     */
    private byte[] toByteArray(Object obj) {
        byte[] bytes = null;
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        try {
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(obj);
            oos.flush();
            bytes = bos.toByteArray();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
        return bytes;
    }

    /**
     * 描述 : byte[]转Object
     * @param bytes
     * @return Object
     */
    private Object toObject(byte[] bytes) {
        Object obj = null;

        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;
        try {
            bis = new ByteArrayInputStream(bytes);
            ois = new ObjectInputStream(bis);
            obj = ois.readObject();
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return obj;
    }


    @Override
    public String incrString( String key) {
        log.debug(" process in RedisServiceImpl.addString method, parameter keyEnum: " + ", key: " + key );

        Long result = null;
        Jedis jedis = redisDataSource.getRedisClient();
        if (jedis != null) {
            String redisKey = key;
            try {
                result = jedis.incr(redisKey);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            } finally {
                redisDataSource.returnResource(jedis);
            }
        }

        return result.toString();
    }

    @Override
    public Long incrBy(String key, long by) {
        return null;
    }


    @Override
    public String incrString(ICacheKey cacheKey, String key) {
        log.debug(" process in RedisServiceImpl.deleteObject method, parameter cacheKey: " + cacheKey + ", key: " + key);

        Long result = null;
        Jedis jedis = redisDataSource.getRedisClient();
        if (jedis != null) {
            String redisKey = this.generateKey(cacheKey, key);
            try {
                result = jedis.incr(redisKey);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            } finally {
                redisDataSource.returnResource(jedis);
            }
        }
        return result.toString();
    }

    @Override
    public String setCounterString(String key, Integer second) {
        return null;
    }

    @Override
    public Boolean limiter(ICacheKey cacheKey,String key) {
        log.debug(" process in RedisServiceImpl.deleteObject method, parameter cacheKey: " + cacheKey + ", key: " + key);

        Boolean result = false;
        Jedis jedis = redisDataSource.getRedisClient();
        if (jedis != null) {
            String redisKey = this.generateKey(cacheKey, key);
            try {
                String current = jedis.get(redisKey);
                if (current != null && Integer.valueOf(current) >= 1){
                    result = false;
                }else{
                    jedis.incr(redisKey);
                    jedis.expire(redisKey,3);
                    result = true;
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            } finally {
                redisDataSource.returnResource(jedis);
            }
        }
        return result;
    }
}
